package gamagora.game.entity.player
{
	import Box2D.Collision.Shapes.b2MassData;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2Fixture;
	
	import gamagora.game.entity.OSBox;
	import gamagora.game.entity.OSCircle;
	import gamagora.game.entity.OSEntity;
	import gamagora.game.entity.OSGround;
	import gamagora.game.entity.OSPhysicEntity;
	import gamagora.game.resources.OSImages;
	import gamagora.game.states.OSPlayState;
	
	import org.flixel.FlxG;
	import org.flixel.FlxPoint;
	import org.flixel.FlxSprite;
	import org.flixel.FlxText;
	import org.flixel.system.FlxAnim;

	public class OSPlayer extends OSCircle
	{
		public static const DEBUG:Boolean = false;
		
		private static const WIDTH:Number = 40;
		private static const HEIGHT:Number = 40;
		private static const RADIUS:Number = 20;
		private static const MASS:Number = 0.2222; // Mass équivalente à un body 20/20 (width/height)
		
		private static const SPEED:Number = 7.0;
		private static const JUMP:Number = 5.0;
		private static const MAX_JUMP_VELOCITY:Number = -8.0;
		private static const MAX_BOUNCED_VELOCITY:Number = -14.0;
		private static const MAX_FIRE_VELOCITY:Number = -4.0;
		
		//Attributs
		private var _playerAnimation:OSPlayerAnimation;
		private var _playerSounds:OSPlayerSounds;
		private var _halfWidth:Number;
		private var _direction:b2Vec2;
		private var _vel:b2Vec2;
		private var _nbPlayerContacts:uint;
		private var _jumpDirection:int;
		private var _askForJump:Boolean;
		private var _canJump:Boolean;
		private var _wallJump:Boolean;
		private var _onGround:Boolean;
		private var _onInclinedGround:Boolean;
		private var _inclineGroundMinY:Number;
		private var _maxJumpVel:Number;
		private var _inclinedAngle:Number;
		
		//Box
		private var _canMoveBox:Boolean;
		private var _boxMinY:Number;
		private var _contactWithBox:Boolean; // va etre utilise pour le sprite push
		
		//Debug
		private var _playerShape:OSEntity;
		private var _velocityText:FlxText;
		private var _askForJumpText:FlxText;
		private var _canJumpText:FlxText;
		private var _wallJumpText:FlxText;
		private var _onGroundText:FlxText;
		private var _onInclinedText:FlxText;
		private var _bouncedText:FlxText;
		private var _nbContactsText:FlxText;
		private var _moveBoxText:FlxText;
		
		public function OSPlayer(X:Number=0, Y:Number=0)
		{
			super(PLAYER, X, Y, RADIUS);
			
			//Chargement des animations
			_playerAnimation = new OSPlayerAnimation(this);
			
			//Sons
			_playerSounds = new OSPlayerSounds(this);
			
			//Nécessaire car le chargement des animations change la taille à 128 et fausse la caméra
			width = WIDTH;
			height = HEIGHT;
			
			//Physique
			_direction = new b2Vec2();
			_bodyDef.allowSleep = false;
			_bodyDef.type = b2Body.b2_dynamicBody;
			_bodyDef.fixedRotation = true;
			_fixDef.friction = 1.0;
			_fixDef.density = 0.5;
			
			//Autres
			_halfWidth = WIDTH / 2;
			facing = LEFT;
			_onInclinedGround = false;
			_inclinedAngle = 0;
			_inclineGroundMinY = 0;
			_boxMinY = 0;
			_maxJumpVel = MAX_JUMP_VELOCITY;
			
			if(DEBUG)
			{
				_playerShape = new OSEntity(NOTHING, x, y, OSImages.PLAYER_SHAPE);
				add(_playerShape);
			}
		}
		
		//Override
		
		public override function addToWorld():void
		{
			super.addToWorld();
			body.SetBullet(true);
			var massData:b2MassData = new b2MassData();
			massData.mass = MASS;
			body.SetMassData(massData);
			
			//Debug
			if(DEBUG)
			{
				_velocityText = OSPlayState.addDebugText();
				_askForJumpText = OSPlayState.addDebugText();
				_canJumpText = OSPlayState.addDebugText();
				_wallJumpText = OSPlayState.addDebugText();
				_onGroundText = OSPlayState.addDebugText();
				_onInclinedText = OSPlayState.addDebugText();
				_bouncedText = OSPlayState.addDebugText();
				_nbContactsText = OSPlayState.addDebugText();
				_moveBoxText = OSPlayState.addDebugText();
			}
		}
		
		public override function update():void
		{
			super.update();
			
			if(DEBUG)
			{
				_playerShape.x = x;
				_playerShape.y = y;
			}
			
			//Mise à jour de l'angle du joueur
			if(_onInclinedGround && y + (0.75 * HEIGHT) > _inclineGroundMinY)
			{
				angle = _inclinedAngle;
			}
			else 
			{
				angle = 0;
			}
			
			//Vitesse
			_vel = body.GetLinearVelocity();
			
			//On ne peut pas sauter si on n'est ni sur le sol ni en walljump ni en train de pousser une caise
			if(!_wallJump && !_onGround && !_canMoveBox)
			{
				_canJump = false;
			}
			
			// Saut
			if((FlxG.keys.justPressed("UP") || FlxG.keys.justPressed("SPACE")) && _canJump) // Si on à le droit de sauter
			{
				_askForJump = true;
				_onGround = false;
				_direction.Set(_jumpDirection * SPEED, -JUMP); //On calcule la direction de saut  
				body.ApplyImpulse(_direction, body.GetPosition()) // On saute	
			}
			
			// Control horizontal
			if(FlxG.keys.pressed("LEFT")) // Gauche
			{
				if (_onInclinedGround || !_wallJump)
				{
					_vel.x = -SPEED; // mouvement vers la gauche
				}
			}
			
			if(FlxG.keys.pressed("RIGHT")) // Droite
			{
				if (_onInclinedGround || !_wallJump)
				{
					_vel.x = SPEED; // mouvement vers la droite
				}
			}
			
			//Linear damping seulement si le player est au sol
			if(_canJump)
			{
				if(_vel.x < 0)
				{
					_vel.x += 0.1;
					if(_vel.x > 0)
					{
						_vel.x = 0;
					}
				}
				else if(_vel.x > 0)
				{
					_vel.x -= 0.1;
					if(_vel.x < 0)
					{
						_vel.x = 0;
					}
				}
			}
			
			if(_askForJump) //Le jouer a explicitement demandé à sauter
			{
				// Borne de vitesse (saut)
				if(_vel.y <= _maxJumpVel)
				{
					_vel.y = _maxJumpVel;
				}
				
				//Borne de vitesse (déplacement lattéral)
				if(_vel.x < -SPEED)
				{
					_vel.x = -SPEED;
				}
				else if(_vel.x > SPEED)
				{
					_vel.x = SPEED;
				}
			}
			else 
			{
				//On est dans la situation ou le joueur prend de la vitesse en tombant d'un mur
				//ou en montant/descendant d'une pente, on veut donc limiter la vitesse à l'extrême
				//pour que le joueur reste bien positionné sur les éléments du jeu (pente, pont, etc.)
				
				if(_vel.y < 0) // On prend de la vitesse en montant
				{
					_vel.y = 0;
				}
				else 
				{
					if(_vel.y > 0.1) // On prend de la vitesse en descendant
					{
						//Si on vient d'arriver sur une pente, on limite la vitesse en x
						//pour que le joueur reste bien sur la pente
						if(_onInclinedGround)
						{
							if(_vel.x < -3)
							{
								_vel.x = -3;
							}
							else if(_vel.x > 3)
							{
								_vel.x = 3;
							}
						}
					}
				}
			}
			
			// La vitesse de descente ne doit pas dépendre de la masse, on applique donc un surplus de force
			// pour faire tomber le joueur plus vite si ce dernier n'est pas en train de descendre une pente
			if (!_onInclinedGround && _vel.y > 0)
			{
				_vel.y += 0.5;
			}
			
			//Mise à jour des animations
			_playerAnimation.update();
			
			//Mise à jour du son
			_playerSounds.update();
			
			//Debug
			if(DEBUG)
			{
				_velocityText.text = "vx = " + _vel.x.toFixed(1) + " vy = " + _vel.y.toFixed(1);
				_askForJumpText.text = "ask for jump = " + _askForJump;
				_canJumpText.text = "can jump = " + _canJump;
				_wallJumpText.text = "wall jump = " + _wallJump;
				_onGroundText.text = "on ground = " + _onGround;
				_onInclinedText.text = "on inclined ground = " + _onInclinedGround;
				_bouncedText.text = "bounced = " + (_maxJumpVel == MAX_BOUNCED_VELOCITY);
				_nbContactsText.text = "nb contacts = " + _nbPlayerContacts;
				_moveBoxText.text = "moving box = " + isMovingBox();
			}
			
		}
		
		public override function onBeginContact(entity:OSPhysicEntity):void
		{
			_nbPlayerContacts++;
			
			_maxJumpVel = MAX_JUMP_VELOCITY;

			if(entity.type == OSEntity.GROUND)
			{
				var gr:OSGround = entity as OSGround;
				_inclinedAngle = gr.grAngle;
				_onInclinedGround = _inclinedAngle != 0;
				if(_onInclinedGround)
				{
					_inclineGroundMinY = entity.fixture.GetAABB().lowerBound.y * OSPhysicEntity.RATIO;
				}
				else 
				{
					_inclineGroundMinY = 0;
				}
				
				if(gr.bounce)
				{
					_maxJumpVel = MAX_BOUNCED_VELOCITY;
					_askForJump = true;
					_onGround = false;
					_wallJump = false;
					
					gr.play(OSGround.BOUNCE, true);
					
					_vel.y = 1; //Nécessaire car à ce stade la vitesse à été stopper par le damping de masse
					_direction.Set((facing == RIGHT ? -1 : 1) * SPEED, -JUMP); //On calcule la direction de saut  
					body.ApplyImpulse(_direction, body.GetPosition()) // On saute	
				}
			}
			else
			{
				if(entity.type == FIRE_GROUND)
				{
					_maxJumpVel = MAX_FIRE_VELOCITY;
					_askForJump = true;
					_onGround = false;
					_wallJump = false;
					
					_vel.y = 1; //Nécessaire car à ce stade la vitesse à été stopper par le damping de masse
					_direction.Set((facing == RIGHT ? -1 : 1) * SPEED, -JUMP); //On calcule la direction de saut  
					body.ApplyImpulse(_direction, body.GetPosition()) // On saute	
				}
				else
				{
					_boxMinY = entity.fixture.GetAABB().lowerBound.y * OSPhysicEntity.RATIO;
					_contactWithBox = entity.type == MOVING_BOX;
					if(_contactWithBox && _boxMinY < y)
					{
						_canMoveBox = true;
					}
				}
			}
			
			if(_maxJumpVel == MAX_JUMP_VELOCITY)
			{
				jumpControl(entity);
			}


			switch(entity.type)
			{
				case OSEntity.GROUND:
					_playerSounds.currentSound = OSPlayerSounds.ON_GROUND;
					break;
				case OSEntity.BRIDGE:
					_playerSounds.currentSound = OSPlayerSounds.ON_WOOD;
					break;
			}
		}
		
		public override function onEndContact(entity:OSPhysicEntity):void
		{
			_nbPlayerContacts--;
			_canJump = _nbPlayerContacts > 0;

			if(_inclinedAngle != 0)
			{
				_canJump = true;
				_wallJump = false;
			}

			if(_nbPlayerContacts == 0)
			{
				/*** REVOIR **************/
				_onInclinedGround = false;
				/*************************/
				if(_askForJump || _body.GetLinearVelocity().y > 0)
				{
					_onGround = false;
					//_onInclinedGround = false;
					_inclinedAngle = 0;
					_inclineGroundMinY = 0;
				}
			}
			
			if(entity.type == MOVING_BOX)
			{
				_contactWithBox = false;
				_canMoveBox = false;
			}
		}
		
		//Methods
		
		public function isMovingBox():Boolean
		{
			return (_onGround && _contactWithBox && Math.abs(_vel.x) > 0.01 && _boxMinY < y);
		}
		
		private function jumpControl(entity:OSPhysicEntity):void
		{
			var obstacle:b2Fixture = entity.fixture;
			
			_wallJump = false;
			
			// Le joueur est posé sur l'obstacle
			if(body.GetPosition().y < obstacle.GetBody().GetPosition().y - obstacle.GetAABB().GetExtents().y)
			{
				//On peut donc sauter en hauteur
				_canJump = true;
				_wallJump = false;
				_jumpDirection = 0;
				_onGround = true;
				_askForJump = false;
			}
			else // Le joueur touche l'obstacle mais n'est pas au dessus
			{
				if (!_onGround && !(entity.type == GROUND && !(entity as OSGround).enableWallJump)) // Pas de WJ dans les coins de map 
				{
					// Est ce que l'on est à gauche de l'obstacle ?
					if(body.GetPosition().x < obstacle.GetBody().GetPosition().x)
					{
						//Si oui on rebondit vers la gauche
						_canJump = true;
						_wallJump = true;
						_jumpDirection = -1;
					}
					else //On est à droite de l'obstacle
					{
						//On rebondit donc vers la droite
						_canJump = true;
						_wallJump = true;
						_jumpDirection = 1;
					}
				}
			}
		}
		
		//Getters Setters

		public function get halfWidth():Number
		{
			return _halfWidth;
		}

		public function get askForJump():Boolean
		{
			return _askForJump;
		}

		public function get canJump():Boolean
		{
			return _canJump;
		}

		public function get wallJump():Boolean
		{
			return _wallJump;
		}

		public function get onGround():Boolean
		{
			return _onGround;
		}

		public function get onInclinedGround():Boolean
		{
			return _onInclinedGround;
		}

		public function get vel():b2Vec2
		{
			return _vel;
		}
		
		public function get curAnim():FlxAnim
		{
			return _curAnim;
		}

		public function get contactWithBox():Boolean
		{
			return _contactWithBox;
		}

		public function get canMoveBox():Boolean
		{
			return _canMoveBox;
		}
	}
}
